winsafe\kernel\utilities/
path.rs

1//! File path utilities.
2//!
3//! Some of the functions are similar to [`std::path::Path`] ones, but here they
4//! work directly upon [`&str`](str) instead of [`&OsStr`](std::ffi::OsStr).
5
6use crate::co;
7use crate::decl::*;
8use crate::kernel::iterators::*;
9use crate::prelude::*;
10
11/// Returns an iterator over the files and folders within a directory.
12/// Optionally, a wildcard can be specified to filter files by name.
13///
14/// This is a high-level abstraction over [`HFINDFILE`](crate::HFINDFILE)
15/// iteration functions.
16///
17/// # Examples
18///
19/// Listing all text files in a directory:
20///
21/// ```no_run
22/// use winsafe::{self as w, prelude::*};
23///
24/// for file_path in w::path::dir_list("C:\\temp", Some("*.txt")) {
25///     let file_path = file_path?;
26///     println!("{}", file_path);
27/// }
28/// # w::SysResult::Ok(())
29/// ```
30#[must_use]
31pub fn dir_list<'a>(
32	dir_path: &'a str,
33	filter: Option<&'a str>,
34) -> impl Iterator<Item = SysResult<String>> + 'a {
35	DirListIter::new(dir_path.to_owned(), filter)
36}
37
38/// Returns an interator over the files within a directory, and all its
39/// subdirectories, recursively.
40///
41/// This is a high-level abstraction over [`HFINDFILE`](crate::HFINDFILE)
42/// iteration functions.
43///
44/// # Examples
45///
46/// ```no_run
47/// use winsafe::{self as w, prelude::*};
48///
49/// // Ordinary for loop
50/// for file_path in w::path::dir_walk("C:\\Temp") {
51///     let file_path = file_path?;
52///     println!("{}", file_path);
53/// }
54///
55/// // Closure with try_for_each
56/// w::path::dir_walk("C:\\Temp")
57///     .try_for_each(|file_path| {
58///         let file_path = file_path?;
59///         println!("{}", file_path);
60///         Ok(())
61///     })?;
62///
63/// // Collecting into a Vec
64/// let all = w::path::dir_walk("C:\\Temp")
65///     .collect::<w::SysResult<Vec<_>>>()?;
66///
67/// // Transforming and collecting into a Vec
68/// let all = w::path::dir_walk("C:\\Temp")
69///     .map(|file_path| {
70///         let file_path = file_path?;
71///         Ok(format!("PATH: {}", file_path))
72///     })
73///     .collect::<w::SysResult<Vec<_>>>()?;
74/// # w::SysResult::Ok(())
75/// ```
76#[must_use]
77pub fn dir_walk<'a>(dir_path: &'a str) -> impl Iterator<Item = SysResult<String>> + 'a {
78	DirWalkIter::new(dir_path.to_owned())
79}
80
81/// Returns a new string with the path of the current EXE file, without the EXE
82/// filename, and without a trailing backslash.
83///
84/// In a debug build, the `target\debug` folders will be suppressed.
85#[cfg(debug_assertions)]
86#[must_use]
87pub fn exe_path() -> SysResult<String> {
88	let dbg = HINSTANCE::NULL.GetModuleFileName()?;
89	Ok(get_path(
90		get_path(
91			get_path(&dbg).unwrap(), // exe name; go up target and debug folders
92		)
93		.unwrap(),
94	)
95	.unwrap()
96	.to_owned())
97}
98
99/// Returns a new string with the path of the current EXE file, without the EXE
100/// filename, and without a trailing backslash.
101///
102/// In a debug build, the `target\debug` folders will be suppressed.
103#[cfg(not(debug_assertions))]
104#[must_use]
105pub fn exe_path() -> SysResult<String> {
106	Ok(get_path(&HINSTANCE::NULL.GetModuleFileName()?)
107		.unwrap()
108		.to_owned())
109}
110
111/// Returns true if the path exists.
112#[must_use]
113pub fn exists(full_path: &str) -> bool {
114	GetFileAttributes(full_path).is_ok()
115}
116
117/// Extracts the file name from a full path, if any.
118///
119/// # Examples
120///
121/// ```no_run
122/// use winsafe::{self as w, prelude::*};
123///
124/// let f = w::path::get_file_name("C:\\Temp\\foo.txt"); // foo.txt
125/// ```
126#[must_use]
127pub fn get_file_name(full_path: &str) -> Option<&str> {
128	match full_path.rfind('\\') {
129		None => Some(full_path), // if no backslash, the whole string is the file name
130		Some(idx) => {
131			if idx == full_path.chars().count() - 1 {
132				None // last char is '\\', no file name
133			} else {
134				Some(&full_path[idx + 1..])
135			}
136		},
137	}
138}
139
140/// Extracts the full path, but the last part.
141///
142/// # Examples
143///
144/// ```no_run
145/// use winsafe::{self as w, prelude::*};
146///
147/// let p = w::path::get_path("C:\\Temp\\xx\\a.txt"); // C:\Temp\xx
148/// let q = w::path::get_path("C:\\Temp\\xx\\");      // C:\Temp\xx
149/// let r = w::path::get_path("C:\\Temp\\xx");        // C:\Temp"
150/// ```
151#[must_use]
152pub fn get_path(full_path: &str) -> Option<&str> {
153	full_path
154		.rfind('\\') // if no backslash, the whole string is the file name, so no path
155		.map(|idx| &full_path[0..idx])
156}
157
158/// Tells whether the full path ends in one of the given extensions,
159/// case-insensitive.
160///
161/// # Examples
162///
163/// ```no_run
164/// use winsafe::{self as w, prelude::*};
165///
166/// println!("{}",
167///     w::path::has_extension("file.txt", &[".txt", ".bat"]));
168/// ```
169#[must_use]
170pub fn has_extension(full_path: &str, extensions: &[impl AsRef<str>]) -> bool {
171	let full_path_u = full_path.to_uppercase();
172	extensions
173		.iter()
174		.find(|ext| {
175			let ext_u = ext.as_ref().to_uppercase();
176			full_path_u.ends_with(&ext_u)
177		})
178		.is_some()
179}
180
181/// Returns true if the path is a directory. Calls
182/// [`GetFileAttributes`](crate::GetFileAttributes).
183///
184/// # Panics
185///
186/// Panics if the path does not exist.
187#[must_use]
188pub fn is_directory(full_path: &str) -> bool {
189	let flags = GetFileAttributes(full_path).unwrap();
190	flags.has(co::FILE_ATTRIBUTE::DIRECTORY)
191}
192
193/// Returns true if the path is hidden. Calls
194/// [`GetFileAttributes`](crate::GetFileAttributes).
195///
196/// # Panics
197///
198/// Panics if the path does not exist.
199#[must_use]
200pub fn is_hidden(full_path: &str) -> bool {
201	let flags = GetFileAttributes(full_path).unwrap();
202	flags.has(co::FILE_ATTRIBUTE::HIDDEN)
203}
204
205/// Replaces the file extension by the given one, returning a new string.
206///
207/// # Examples
208///
209/// ```no_run
210/// use winsafe::{self as w, prelude::*};
211///
212/// let p = w::path::replace_extension(
213///     "C:\\Temp\\something.txt", ".sh"); // C:\Temp\something.sh
214/// ```
215#[must_use]
216pub fn replace_extension(full_path: &str, new_extension: &str) -> String {
217	if let Some(last) = full_path.chars().last() {
218		if last == '\\' {
219			return rtrim_backslash(full_path).to_owned(); // full_path is a directory, do nothing
220		}
221	}
222
223	let new_has_dot = new_extension.chars().next() == Some('.');
224	match full_path.rfind('.') {
225		None => format!(
226			"{}{}{}", // file name without extension, just append it
227			full_path,
228			if new_has_dot { "" } else { "." },
229			new_extension,
230		),
231		Some(idx) => {
232			format!("{}{}{}", &full_path[0..idx], if new_has_dot { "" } else { "." }, new_extension,)
233		},
234	}
235}
236
237/// Replaces the file name by the given one, returning a new string.
238#[must_use]
239pub fn replace_file_name(full_path: &str, new_file: &str) -> String {
240	match get_path(full_path) {
241		None => new_file.to_owned(),
242		Some(path) => format!("{}\\{}", path, new_file),
243	}
244}
245
246/// Keeps the file name and replaces the path by the given one, returning a new
247/// string.
248///
249/// # Examples
250///
251/// ```no_run
252/// use winsafe::{self as w, prelude::*};
253///
254/// let p = w::path::replace_path( // C:\another\foo.txt
255///     "C:\\Temp\\foo.txt",
256///     "C:\\another",
257/// );
258/// ```
259#[must_use]
260pub fn replace_path(full_path: &str, new_path: &str) -> String {
261	let file_name = get_file_name(full_path);
262	format!(
263		"{}{}{}",
264		rtrim_backslash(new_path),
265		if file_name.is_some() { "\\" } else { "" },
266		file_name.unwrap_or("")
267	)
268}
269
270/// Removes a trailing backslash, if any.
271///
272/// # Examples
273///
274/// ```no_run
275/// use winsafe::{self as w, prelude::*};
276///
277/// let p = w::path::rtrim_backslash("C:\\Temp\\"); // C:\Temp
278/// ```
279#[must_use]
280pub fn rtrim_backslash(full_path: &str) -> &str {
281	match full_path.chars().last() {
282		None => full_path, // empty string
283		Some(last_ch) => {
284			if last_ch == '\\' {
285				let mut chars = full_path.chars();
286				chars.next_back(); // remove last char
287				chars.as_str()
288			} else {
289				full_path // no trailing backslash
290			}
291		},
292	}
293}
294
295/// Returns a `Vec` with each part of the full path.
296#[must_use]
297pub fn split_parts(full_path: &str) -> Vec<&str> {
298	let no_bs = rtrim_backslash(full_path);
299	no_bs.split('\\').collect()
300}